/* $ZEL: sis1100_ddma_map_linux.c,v 1.1 2005/07/07 14:19:17 wuestner Exp $ */

/*
 * Copyright (c) 2005
 * 	Peter Wuestner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#undef DEBUG
#include "sis1100_sc.h"

void
sis1100_ddma_zero(struct demand_dma_block* block)
{
    block->uaddr=0;
    block->sglist=0;
    block->nr_pages=0;
    block->sgcount=0;
    block->desc_pages=0;
}

void
sis1100_ddma_unmap_block(struct sis1100_softc *sc,
        struct demand_dma_block* block)
{
    int i;

    if (block->desc_pages) {
        for (i=0; i<block->dsegs; i++) {
            if (block->desc_pages[i].cpu_addr)
                pci_free_consistent(sc->pcidev, PAGE_SIZE,
                    block->desc_pages[i].cpu_addr,
                    block->desc_pages[i].dma_handle);
        }
        vfree(block->desc_pages);
    }

    if (block->sgcount)
        pci_unmap_sg(sc->pcidev, block->sglist,
            block->nr_pages, PCI_DMA_FROMDEVICE);

    if (block->nr_pages)
        sgl_unmap_user_pages(block->sglist, block->nr_pages, 0);

    if (block->sglist)
        vfree(block->sglist);
}

int
sis1100_ddma_map_block(struct sis1100_softc *sc,
    struct demand_dma_block* block)
{
    int dpseg, i;

    pINFO(sc, "dma_map_block: size=%lld addr=%p",
            (unsigned long long)block->size, block->uaddr);

    /* number of user pages (we require start and end at page boundaries) */
    block->nsegs=block->size>>PAGE_SHIFT;

    pDEBUG(sc, "dma_map_block: demand_dma.pages=%d", block->nsegs);

    /* alloc memory for the scatterlist */
    block->sglist=vmalloc(block->nsegs*
            sizeof(struct scatterlist));
    if (!block->sglist) {
        pERROR(sc, "dma_map_block: cannot vmalloc sglist for %d pages (%lld byte)",
            block->nsegs,
            (unsigned long long)(block->nsegs*sizeof(struct scatterlist)));
        return ENOMEM;
    }
    pDEBUG(sc, "dma_map: demand_dma.sglist=%p", block->sglist);

    /* wire the user pages */
    block->nr_pages=sgl_map_user_pages(block->sglist,
            block->nsegs, block->uaddr, block->size, READ);
    if (block->nr_pages<0) {
        pERROR(sc, "dma_map: sgl_map_user_pages for %d pages failed",
            block->nsegs);
        return -block->nr_pages;
    }
    pDEBUG(sc, "dma_map: mapped %d pages in %d pieces", block->nsegs,
            block->nr_pages);

    /* map the user pages for DMA */
    block->sgcount=pci_map_sg(sc->pcidev, block->sglist,
            block->nr_pages, PCI_DMA_FROMDEVICE);
    if (!block->sgcount) {
        pERROR(sc, "dma_map: pci_map_sg failed");
        return EIO;
    }
    pDEBUG(sc, "dma_map: mapped %d pieces for DMA", block->sgcount);

    /* pages needed for desriptors */
    dpseg = NBPG / sizeof(struct plx9054_dmadesc); /* desriptors per page */
    block->dsegs = block->sgcount / dpseg +1;

    /* alloc memory to store the addresses of desriptor pages */
    block->desc_pages=vmalloc(block->dsegs* sizeof(struct sis1100_dmapage));
    if (!block->desc_pages) {
        pERROR(sc, "dma_map: vmalloc desc_pages (%d pages) failed",
                block->dsegs);
        return ENOMEM;
    }
    memset(block->desc_pages, 0, block->dsegs* sizeof(struct sis1100_dmapage));
    pDEBUG(sc, "dma_map: demand_dma.desc_pages=%p", block->desc_pages);

    /* allocate the desriptor pages */
    for (i=0; i<block->dsegs; i++) {
        struct sis1100_dmapage* buf=block->desc_pages+i;
        pDEBUG(sc, "dma_map: i=%d buf=%p", i, buf);
        buf->cpu_addr=pci_alloc_consistent(sc->pcidev, PAGE_SIZE,
                &buf->dma_handle);
        pDEBUG(sc, "dma_map: buf->cpu_addr=%p buf->dma_handle=%08llx",
                buf->cpu_addr, (unsigned long long)(buf->dma_handle));
        if (!buf->cpu_addr) {
            pERROR(sc, "dma_map: pci_alloc_consistent page[%d] failed", i);
            return ENOMEM;
        }
    }

    /* fill descriptor buffer for PLX */
    {
        int id; /* index of descriptor in s/g list */
        int ip; /* index of page for descriptors */
        int ii; /* index of descriptor in page */
        struct scatterlist* sg;

        block->dmadpr0=block->desc_pages[0].dma_handle;

        sg=block->sglist;
        ip=0;
        ii=0;
        for (id=0; id<block->sgcount; id++, sg++) {
            u_int32_t next;

            struct plx9054_dmadesc* plx_desc =
                (struct plx9054_dmadesc*)
                    (block->desc_pages[ip].cpu_addr+ii*sizeof(struct plx9054_dmadesc));
            ii++;
            if (ii>=dpseg) {
                ip++;
                ii=0;
            }
            if (id<block->sgcount-1) {
                next=(block->desc_pages[ip].dma_handle+ii*sizeof(struct plx9054_dmadesc))|
                        0x9;
            } else {
                next=0xb;
            }
            plx_desc->pcistart   = cpu_to_le32(sg_dma_address(sg));
            plx_desc->size       = cpu_to_le32(sg_dma_len(sg));
            plx_desc->localstart = cpu_to_le32(0);
            plx_desc->next       = cpu_to_le32(next);

#ifdef DEBUG            
            if (id<3) {
                pERROR(sc, "id=%d", id);
                pERROR(sc, "desc      =%p",     plx_desc);
                pERROR(sc, "pcistart  =0x%08x", plx_desc->pcistart);
                pERROR(sc, "size      =0x%08x", plx_desc->size);
                pERROR(sc, "localstart=0x%08x", plx_desc->localstart);
                pERROR(sc, "next      =0x%08x", plx_desc->next);
            }
#endif
        }
    }

    return 0;
}
